using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.Carto;
using Bes.Swsp.ToolBox;

namespace BesAsm.Swsp.GpTools
{
  public class Program
  {
    private static LicenseInitializer m_AOLicenseInitializer = new BesAsm.Swsp.GpTools.LicenseInitializer();
    private static IGPUtilities2 gpUtility;
  
    [STAThread()]
    public static int Main(string[] args)
    {
      //ESRI License Initializer generated code.
      m_AOLicenseInitializer.InitializeApplication(new esriLicenseProductCode[] { esriLicenseProductCode.esriLicenseProductCodeAdvanced },
      new esriLicenseExtensionCode[] { esriLicenseExtensionCode.esriLicenseExtensionCodeSpatialAnalyst });

      gpUtility = new GPUtilitiesClass();

      Logger.StartLogging();
      Logger.Logged += Logger_Logged;
      

      if (args.Length == 0)
      {
        Logger.Log("Specify a tool to execute. Valid tools are: {'Accumulate', 'Decumulate, 'PolylineRasterIntersect'}");        
        return -1;
      }

      try
      {
        string tool = args[0];

        switch (tool)
        {
          case "Accumulate":
            Logger.Log("Executing 'Accumulate'...");
            Accumulate(args);
            break;
          case "Decumulate":
            Logger.Log("Executing 'Decumulate'...");
            Decumulate(args);
            break;
          case "PolylineRasterIntersect":
            Logger.Log("Executing 'PolylineRasterIntersect'...");
            PolylineRasterIntersect(args);
            break;
          case "Test":
            for (int i = 0; i < args.Length; i++)
              Console.WriteLine(args[i].ToString());
            
            break;
          default:
            break;
        }
      }
      catch (Exception ex)
      {
        Logger.Log("Error: " + ex.Message);
        return -1;
      }
      finally
      {                     
        Logger.StopLogging();
      }

      //ESRI License Initializer generated code.
      //Do not make any call to ArcObjects after ShutDownApplication()
      m_AOLicenseInitializer.ShutdownApplication();

      Console.WriteLine("Application terminated.");
      return 0;
    }

    private static void PolylineRasterIntersect(string[] args)
    {
      if (args.Length <4)
      {
        Logger.Log("'PolylineRasterIntersect' syntax: PolylineRasterIntersect line_featureclass raster rasterField prefix");
        return;
      }

      string conveyances = args[1];
      
      string rasterPath = args[2];
      string rasterField = args[3];

      string prefix;
      if (args.Length == 5)
        prefix = args[4];
      else
        prefix = "";

      string argString = FormatArgString(args);

      PolylineRasterIntersect intersector = new PolylineRasterIntersect(false);

      IRaster raster = gpUtility.OpenRasterDatasetFromString(rasterPath).CreateDefaultRaster();
      IFeatureClass lines = gpUtility.OpenFeatureClassFromString(conveyances);

      intersector.CategoryRaster = raster;
      intersector.RasterField = rasterField;
      intersector.PolylineClass = lines;
      intersector.PolylineIdField = "Id";
      intersector.OutputFieldPrefix = prefix;

      intersector.Intersect();
      

    }    

    static void Accumulate(string[] args)
    {
      if (args.Length != 8)
      {
        Logger.Log("'Accumulate' syntax: Accumulate conveyance_path node_path catchment_path flow_dir_path local_accumulation_path accumulation_field_name factor");
        return;
      }
      
      string conveyances = args[1];
      string nodes = args[2];
      string catchments = args[3];
      string flowDir = args[4];
      string localAccumulation = args[5];
      string accumulationFieldName = args[6];
      double factor = Convert.ToDouble(args[7]);

      string argString = FormatArgString(args);

      Logger.Log("Instantiating Stormwater Toolbox with arguments:\n" + argString);
      StormDrainNetworkLoader loader = GetNetworkLoader(conveyances, nodes, catchments, flowDir);

      Bes.Swsp.ToolBox.ToolBox swspToolbox = GetToolbox(loader);
      
      IRaster localAccumulationRaster = gpUtility.OpenRasterDatasetFromString(localAccumulation).CreateDefaultRaster();
      
      swspToolbox.AccumulateThroughNetwork(localAccumulationRaster);

      loader.FlowAccumulationField = accumulationFieldName;
      loader.UpdateStormNetwork(factor);

      return;
    }

    static void Decumulate(string[] args)
    {
      if (args.Length != 12)
      {
        Logger.Log("'Decumulate' syntax: Decumulate conveyance_path node_path catchment_path prf_table flow_field_name flow_factor load_field_name load_factor decumulated_flow_field decumulated_load_field");
        return;
      }

      string conveyances = args[1];
      string nodes = args[2];
      string catchments = args[3];
      string prfTable = args[4];
      string constituent = args[5];
      string flowField = args[6];
      double flowFactor = Convert.ToDouble(args[7]);
      string loadField = args[8];
      double loadFactor = Convert.ToDouble(args[9]);
      string outFlowField = args[10];
      string outLoadField = args[11];

      string argString = FormatArgString(args);

      Logger.Log("Instantiating Stormwater Toolbox with arguments:\n" + argString);
      StormDrainNetworkLoader loader = GetNetworkLoader(conveyances, nodes, catchments, string.Empty);
      loader.LoadTotalAccumulation(flowField, loadField, flowFactor, loadFactor);

      Bes.Swsp.ToolBox.ToolBox swspToolbox = GetToolbox(loader);

      int[] nodeIds;
      double[] volumeReductionPercent;
      double[] effluentConcentration;
      ReadPrfData(loader, prfTable, constituent, out nodeIds, out volumeReductionPercent, out effluentConcentration);

      swspToolbox.Decumulate(nodeIds, volumeReductionPercent, effluentConcentration);

      loader.FlowAccumulationField = outFlowField;
      loader.LoadAccumulationField = outLoadField;
      loader.UpdateStormNetwork(1.0/flowFactor, 1.0/loadFactor);

      return;
    }

    private static string FormatArgString(string[] args)
    {
      string argString = "";

      for (int i = 0; i < args.Length; i++)
        argString = argString + " " + args[i] + "\n";
      return argString;
    }

    private static ToolBox GetToolbox(StormDrainNetworkLoader loader)
    {
      Bes.Swsp.ToolBox.ToolBox swspToolbox;
      swspToolbox = new Bes.Swsp.ToolBox.ToolBox();

      swspToolbox.StormNetwork = loader.StormNetwork;
      swspToolbox.FlowDirRaster = loader.FlowDirRaster;
      return swspToolbox;
    }

    private static StormDrainNetworkLoader GetNetworkLoader(string conveyances, string nodes, string catchments, string flowDir)
    {            
      StormDrainNetworkLoader loader = new StormDrainNetworkLoader();

      loader.StormNetworkConveyanceClass = gpUtility.OpenFeatureClassFromString(conveyances);
      loader.StormNetworkNodeClass = gpUtility.OpenFeatureClassFromString(nodes);

      loader.CatchmentClass = gpUtility.OpenFeatureClassFromString(catchments);

      if (!string.IsNullOrWhiteSpace(flowDir))
      {
        IRaster flowDirRaster = gpUtility.OpenRasterDatasetFromString(flowDir).CreateDefaultRaster();
        loader.FlowDirRaster = flowDirRaster;
      }

      loader.LoadStormNetwork();

      return loader;
    }

    private static void ReadPrfData(StormDrainNetworkLoader loader, string prfTablePath, string constituent, out int[] nodeIds, out double[] volumeReductionPercent, out double[] effluentConcentration)
    {
      const string volumeConstituent = "Volume Reduction";
      const string prfTypeFieldName = "prf_type_id";
      const string nodeIdFieldName = "Id";

      Dictionary<int, double> concentrationLookup = ReadLookupTable(prfTablePath, constituent);
      Dictionary<int, double> volumeReduceLookup = ReadLookupTable(prfTablePath, volumeConstituent);

      // List concentrations and volume reduction for each node
      List<int> nodeIdList = new List<int>();
      List<double> volumeReducList = new List<double>();
      List<double> concentrationList = new List<double>();

      int idField = loader.StormNetworkNodeClass.FindField(nodeIdFieldName);
      int prfTypeField = loader.StormNetworkNodeClass.FindField(prfTypeFieldName);
      IFeatureCursor cursor = loader.StormNetworkNodeClass.Search(null, false);
      try
      {
        IFeature node = cursor.NextFeature();
        while (node != null)
        {
          try
          {
            object dbValue = node.get_Value(prfTypeField);
            if (dbValue != DBNull.Value)
            {
              int prfType = Convert.ToInt32(dbValue);
              int id = Convert.ToInt32(node.get_Value(idField));

              nodeIdList.Add(id);
              volumeReducList.Add(volumeReduceLookup[prfType]);
              concentrationList.Add(concentrationLookup[prfType]);
            }
          }
          finally
          {
            ComUtility.ReleaseComObject(node);
          }
          node = cursor.NextFeature();
        }
      }
      finally
      {
        ComUtility.ReleaseComObject(cursor);
      }

      nodeIds = nodeIdList.ToArray();
      volumeReductionPercent = volumeReducList.ToArray();
      effluentConcentration = concentrationList.ToArray();
    }

    static Dictionary<int, double> ReadLookupTable(string prfTablePath, string constituent)
    {
      const string prfTypeFieldName = "gen_id";
      const string constituentFieldName = "short_name";
      const string concentrationFieldName = "effluent_concentration_mean";

      // Read in standard effluent concentrations/volume reduction for each PRF type.
      Dictionary<int, double> lookupTable = new Dictionary<int, double>();

      ITable table = gpUtility.OpenTableFromString(prfTablePath);
      try
      {
        int prfTypeField = table.FindField(prfTypeFieldName);
        int concentrationField = table.FindField(concentrationFieldName);

        IQueryFilter filter = new QueryFilterClass();
        filter.WhereClause = string.Format("{0} = '{1}'", constituentFieldName, constituent);
        ICursor cursor = table.Search(filter, false);
        try
        {
          IRow row = cursor.NextRow();
          while (row != null)
          {
            try
            {
              int prfType = Convert.ToInt32(row.get_Value(prfTypeField));
              object dbValue = row.get_Value(concentrationField);
              double concentration = dbValue == DBNull.Value ? double.NaN : Convert.ToDouble(dbValue);
              lookupTable[prfType] = concentration;
            }
            finally
            {
              ComUtility.ReleaseComObject(row);
            }
            row = cursor.NextRow();
          }
        }
        finally
        {
          ComUtility.ReleaseComObject(cursor);
        }
      }
      finally
      {
        ComUtility.ReleaseComObject(table);
      }

      return lookupTable;
    }

    static void Logger_Logged(object sender, LoggerEventArgs e)
    {
      Console.WriteLine(e.Message);
    }
  }
}
